﻿using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Text;
using System.Runtime.InteropServices;

using System.IO.Pipes;

namespace omaura_oled
{
    /// <summary>
    /// Class to get data from the omaura Oled Api
    /// 
    /// pipe version
    /// </summary>
    public class ApiInterface
    {
        #region "Parameters"
        //-- parent interface
        private OmauraOled OledInterface;

        private Server pipeServer;

        //-- error message:
        private string ErrorParameters = "parameters problem:";
        private string ErrorMissingParameter = "parameter(s) missing:";
        private string ErrorUnknown = "command not recognized:";

        public bool Interrupt = false;

        public string Status = "";
        public string Session = "";
        public string Title = "";
        public int MediaTime = 0;
        public int MediaTotalTime = 0;

        //--
        #endregion "Parameters"

        #region "Constructors"
        //--
        public ApiInterface(OmauraOled parentOled)
        {
            OledInterface = parentOled;
        }

        //--
        #endregion "Constructors"

        #region "Methods"
        //--

        public void API_Start()
        {
            OledInterface.timerTimeCurrent.Stop();
            //OledInterface.displayClass.displayTitle("pipe created");

            this.pipeServer = new Server();

            this.pipeServer.MessageReceived +=
                new Server.MessageReceivedHandler(pipeServer_MessageReceived);

            //this.pipeServer.PipeName = Pipe_Name;
            this.pipeServer.Start();
        }
        public void API_Stop()
        {
            //MvdM: resolve a crash by checking for non-initialized pipeServer
            if ( this.pipeServer != null )
                this.pipeServer.Stop();
        }

        void pipeServer_MessageReceived(Server.Client client, string message)
        {
            DisplayMessageReceived(client, message);

            /*/
            this.Invoke(new Server.MessageReceivedHandler(DisplayMessageReceived),
                new object[] { client, message });
            */
        }

        //-- analyse the message received
        // in charge og the display
        void DisplayMessageReceived(Server.Client client, string message)
        {
            string[] messageParts = message.Split(' ');

            string cmd = messageParts[0];

            #region "Session"
            //-- session
            if (cmd == "session")
            {
                if (messageParts.Length == 2)
                {
                    string sessionType = messageParts[1];

                    switch (sessionType)
                    {
                        case "home":
                            Session = sessionType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayBkgndHome();
                            break;

                        case "home2":
                            Session = sessionType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayBkgndHome2();
                            break;

                        case "guide":
                            Session = sessionType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayBkgndGuide();
                            break;

                        case "dvd":
                            Session = sessionType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayBkgndDVD();
                            break;

                        case "cd":
                            Session = sessionType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayBkgndCD();
                            break;

                        case "pvr":
                            Session = sessionType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayBkgndPVR();
                            break;

                        case "music":
                            Session = sessionType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayBkgndAudio();
                            break;

                        case "video":
                            Session = sessionType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayBkgndVideo();
                            break;

                        case "tv":
                            Session = sessionType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayBkgndTVTuner();
                            break;

                        case "pictures":
                            Session = sessionType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayBkgndPhotos();
                            break;

                        case "radio":
                            Session = sessionType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayBkgndRadio();
                            break;

                        default:
                            ApiLog.Write(ErrorParameters + message);
                            break;
                    }
                }
                else
                    ApiLog.Write(ErrorMissingParameter + message);
            }

            //--
            #endregion "Session"

            #region "Status"
            //-- status
            else if (cmd == "status")
            {
                if (messageParts.Length == 2)
                {
                    string statusType = messageParts[1];

                    switch (statusType)
                    {
                        case "play":
                            Status = statusType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayIconPlay();
                            break;

                        case "pause":
                            Status = statusType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayIconPause();
                            break;

                        case "stop":
                            Status = statusType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayIconStop();
                            break;

                        case "record":
                            Status = statusType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayIconRecord();
                            break;

                        case "rewind":
                            Status = statusType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayIconRW();
                            break;

                        case "forward":
                            Status = statusType;
                            if (!Interrupt)
                                OledInterface.displayClass.displayIconFF();
                            break;

                        default:
                            ApiLog.Write(ErrorParameters + message);
                            break;
                    }
                }
                else
                    ApiLog.Write(ErrorMissingParameter + message);
            }
            #endregion "Status"

            #region "Title"
            //-- title
            else if (cmd == "title")
            {
                if (messageParts.Length >= 2)
                {
                    char[] stringTrim = "title ".ToCharArray();
                    //string titleText = message.TrimStart(stringTrim);
                    string titleText = "";

                    for (int i = 1; i < messageParts.Length; i++)
                    {
                        titleText += messageParts[i] + " ";
                    }

                    Title = titleText;

                    if (!Interrupt)
                        OledInterface.displayClass.displayTitle(titleText);
                }
                else
                    ApiLog.Write(ErrorMissingParameter + message);
            }

            #endregion "Title"

            #region "Time&progress"
            //-- time
            else if (cmd == "time")
            {
                if (messageParts.Length == 3)
                {
                    int time = Convert.ToInt32(messageParts[1]);
                    int timeTotal = Convert.ToInt32(messageParts[2]);

                    if (timeTotal >= Math.Abs(time)
                        && timeTotal > 0)
                    {
                        MediaTime = time;
                        MediaTotalTime = timeTotal;

                        if (!Interrupt)
                        {
                            if (MediaTime > 0)
                            {
                                OledInterface.displayClass.displayTimeAndTotal_API(time, timeTotal);
                                OledInterface.displayClass.displayProgressBar_API(time, timeTotal);
                            }
                            else
                            {
                                OledInterface.displayClass.displayTimeAndTotal_API(time, timeTotal);
                                OledInterface.displayClass.displayProgressBar_API(time + timeTotal, timeTotal);
                            }
                        }
                    }
                    else
                        ApiLog.Write(ErrorParameters + message);
                }
                else
                    ApiLog.Write(ErrorMissingParameter + message);
            }

            #endregion "Time&progress"

            #region "Volume"
            //-- volume
            else if (cmd == "volume")
            {
                int volume;

                if (messageParts.Length == 2)
                {
                    volume = Convert.ToInt32(messageParts[1]);

                    if (!Interrupt)
                        OledInterface.displayClass.displayVolume_API(volume);
                }
                else
                    ApiLog.Write(ErrorParameters + message);
            }

            #endregion "Volume"

            #region "Weather"
            //--
            else if (cmd == "weather")
            {
                if (messageParts.Length == 1)
                {
                    if (!Interrupt)
                        OledInterface.displayClass.displayWeather_API();
                }
                else
                    ApiLog.Write(ErrorParameters + message);
            }

            #endregion "Weather

            #region "Erase"
            //--
            else if (cmd == "erase")
            {
                if (messageParts.Length == 1)
                {
                    if (!Interrupt)
                        OledInterface.displayClass.eraseScreen_API();
                }
                else
                    ApiLog.Write(ErrorParameters + message);
            }

            #endregion "Erase"

            #region "Clock"
            //--
            else if (cmd == "clock")
            {
                if (messageParts.Length == 2)
                {
                    string clockCmd = messageParts[1];

                    switch (clockCmd)
                    {
                        case "start":
                            if (!Interrupt)
                            {
                                OledInterface.displayClass.eraseScreen_API();
                                OledInterface.timerTimeCurrent.Start();
                            }
                            break;

                        case "stop":
                            if (!Interrupt)
                            {
                                OledInterface.timerTimeCurrent.Stop();
                                OledInterface.displayClass.eraseScreen_API();
                            }
                            break;

                        default:
                            ApiLog.Write(ErrorParameters + message);
                            break;
                    }
                }
                else
                    ApiLog.Write(ErrorParameters + message);
            }

            #endregion "Clock"

            //-- else
            else
                ApiLog.Write(ErrorUnknown + message);
        }

        //--
        #endregion "Methods"
    }

    class Server
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern SafeFileHandle CreateNamedPipe(
           String pipeName,
           uint dwOpenMode,
           uint dwPipeMode,
           uint nMaxInstances,
           uint nOutBufferSize,
           uint nInBufferSize,
           uint nDefaultTimeOut,
           IntPtr lpSecurityAttributes);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern int ConnectNamedPipe(
           SafeFileHandle hNamedPipe,
           IntPtr lpOverlapped);

        public const uint DUPLEX = (0x00000003);
        public const uint FILE_FLAG_OVERLAPPED = (0x40000000);

        public class Client
        {
            public SafeFileHandle handle;
            public FileStream stream;

            public void Disconnect()
            {
                stream.Dispose();
                handle.Dispose();
            }
        }

        public delegate void MessageReceivedHandler(Client client, string message);

        public event MessageReceivedHandler MessageReceived;
        public const int BUFFER_SIZE = 1024;

        string pipeName = "\\\\.\\pipe\\omauraOledPipe";
        Thread listenThread;
        bool running;
        List<Client> clients;

        SafeFileHandle clientHandle;

        public string PipeName
        {
            get { return this.pipeName; }
            set { this.pipeName = value; }
        }

        public bool Running
        {
            get { return this.running; }
        }

        public Server()
        {
            this.clients = new List<Client>();
        }

        /// <summary>
        /// Starts the pipe server
        /// </summary>
        public void Start()
        {
            //start the listening thread
            this.listenThread = new Thread(new ThreadStart(ListenForClients));
            this.listenThread.Start();

            this.running = true;
        }

        /// <summary>
        /// Stop thread
        /// </summary>
        public void Stop()
        {
            this.running = false;

            this.listenThread.Abort();

            if(!clientHandle.IsClosed)
                this.clientHandle.Dispose();

            for (int i = 0; i < clients.Count; i++)
                clients[i].Disconnect();

        }

        /// <summary>
        /// Listens for client connections
        /// </summary>
        private void ListenForClients()
        {
            while (running)
            {
                clientHandle =
                CreateNamedPipe(
                     this.pipeName,
                     DUPLEX | FILE_FLAG_OVERLAPPED,
                     0,
                     255,
                     BUFFER_SIZE,
                     BUFFER_SIZE,
                     0,
                     IntPtr.Zero);

                //could not create named pipe
                if (clientHandle.IsInvalid)
                    return;

                int success = ConnectNamedPipe(clientHandle, IntPtr.Zero);

                //could not connect client
                if (success == 0)
                    return;

                Client client = new Client();
                client.handle = clientHandle;

                lock (clients)
                    this.clients.Add(client);

                Thread readThread = new Thread(new ParameterizedThreadStart(Read));
                readThread.Start(client);
            }
        }

        /// <summary>
        /// Reads incoming data from connected clients
        /// </summary>
        /// <param name="clientObj"></param>
        private void Read(object clientObj)
        {
            Client client = (Client)clientObj;
            client.stream = new FileStream(client.handle, FileAccess.ReadWrite, BUFFER_SIZE, true);
            byte[] buffer = new byte[BUFFER_SIZE];
            //ASCIIEncoding encoder = new ASCIIEncoding();
            UnicodeEncoding encoder = new UnicodeEncoding();

            while (running)
            {
                int bytesRead = 0;

                try
                {
                    bytesRead = client.stream.Read(buffer, 0, BUFFER_SIZE);
                }
                catch
                {
                    //read error has occurred
                    break;
                }

                //client has disconnected
                if (bytesRead == 0)
                    break;

                //fire message received event
                if (this.MessageReceived != null)
                    this.MessageReceived(client, encoder.GetString(buffer, 0, bytesRead));
            }

            //clean up resources
            client.stream.Close();
            client.handle.Close();
            lock (this.clients)
                this.clients.Remove(client);
        }

        /// <summary>
        /// Sends a message to all connected clients
        /// </summary>
        /// <param name="message">the message to send</param>
        public void SendMessage(string message)
        {
            lock (this.clients)
            {
                //ASCIIEncoding encoder = new ASCIIEncoding();
                UnicodeEncoding encoder = new UnicodeEncoding();
                byte[] messageBuffer = encoder.GetBytes(message);
                foreach (Client client in this.clients)
                {
                    client.stream.Write(messageBuffer, 0, messageBuffer.Length);
                    client.stream.Flush();
                }
            }
        }
    }
}
